package com.discovery.main;

import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.dom4j.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author bpaul
 *
 */
public class DiscoveryManager
{
   private Document  templateXML  = null;
   private XmlParser configParser = null;
   private String    configFile   = "com/discovery/main/xml/vShareTemplate.xml";
   private Node      arrayNode    = null;

   /**
    * @throws Exception
    */
   public DiscoveryManager()
                            throws Exception
   {
      this.configParser = getConfigParser();
      arrayNode = configParser.getNode("//Arrays/Array", null);
   }

   /**
    * @param name
    * @return
    * @throws Exception
    */
   public boolean setArrayName(String name)
      throws Exception
   {
      Node array = configParser.getNode("arrayname", arrayNode);
      array.getFirstChild().setNodeValue(name);
      return false;
   }

   /**
    * Adds a fileSystem tag into vShare1.xml
    *
    * @param fileSystem
    * @return
    */
   public boolean addFileSystem(FileSystem fileSystem)
   {
      Node result = null;
      try {
         /*
          * TODO: check if filesystem parth and servername/ipaddress combination
          * already exists or not if exists then throw Exception
          */
         Node fileSystemNode = configParser.getNode("storagefilesystems",
                  arrayNode);
         Node fileSystemChildNode = configParser.getNode("storagefilesystem",
                  fileSystemNode);
         Node newFileSystemNode = fileSystemChildNode.cloneNode(true);

         setNodeValue("filesystemPath", newFileSystemNode, fileSystem
                  .getFileSystemPath());
         setNodeValue("fileservername", newFileSystemNode, fileSystem
                  .getFileServerName());
         setNodeValue("ipaddress", newFileSystemNode, fileSystem.getIpAddress());
         setNodeValue("filesysteminterface", newFileSystemNode, fileSystem
                  .getFileSystemInterface());
         setNodeValue("fileSystemInterfaceVersion", newFileSystemNode,
                  fileSystem.getFileSystemInterfaceVersion());
         setNodeValue("nativeSnapshotSupported", newFileSystemNode, fileSystem
                  .getNativeSnapshotSupported());
         setNodeValue("thinProvisionedStatus", newFileSystemNode, fileSystem
                  .getThinProvisionedStatus());
         setNodeValue("capabilityName", newFileSystemNode, fileSystem
                  .getCapabilityName());
         setNodeValue("capabilityDetail", newFileSystemNode, fileSystem
                  .getCapabilityDetail());

         result = fileSystemNode.appendChild(newFileSystemNode);

      } catch (Exception e) {
         System.out.println(e.getStackTrace());
      }
      return result != null;
   }

   /**
    * @param lun
    * @return
    * @throws Exception
    */
   public boolean addLun(Lun lun)
      throws Exception
   {
      boolean success = false;
      /*
       * Get the luns node from template xml
       */
      Node lunsNode = configParser.getNode("luns", arrayNode);
      Node lunChildNode = configParser.getNode("lun", lunsNode);
      Node newLunNode = lunChildNode.cloneNode(true);

      setNodeValue("id", newLunNode, lun.getId());
      setNodeValue("esxLunIdentifier", newLunNode, lun.getEsxLunIdentifier());
      setNodeValue("capacityInMB", newLunNode, lun.getCapacityInMB());
      setNodeValue("alternateIdentifier", newLunNode, lun
               .getAlternateIdentifier());
      setNodeValue("displayName", newLunNode, lun.getDisplayName());
      setNodeValue("thinProvisioned", newLunNode, lun.getThinProvisioned());
      setNodeValue("thinProvisionedStatus", newLunNode, lun
               .getThinProvisionedStatus());
      setNodeValue("usedSpaceInMB", newLunNode, lun.getUsedSpaceInMB());
      setNodeValue("alternateIdentifier", newLunNode, lun
               .getAlternateIdentifier());
      setNodeValue("drsManagementPermitted", newLunNode, lun
               .getDrsManagementPermitted());
      setNodeValue("remoteReplication", newLunNode, lun.getRemoteReplication());
      setNodeValue("capabilityName", newLunNode, lun.getCapabilityName());
      setNodeValue("capabilityDetail", newLunNode, lun.getCapabilityDetail());
      setNodeValue("transportType", newLunNode, lun.getTransportType());

      addLunToAPort(lun.getId());

      Node result = lunsNode.appendChild(newLunNode);
      if (result != null)
         success = true;
      return success;

   }

   public boolean addLunToAPort(String lunId)
      throws Exception
   {
      Node portChildNode = configParser.getNode(
               "processors/processor/ports/port", arrayNode);
      String lunIds = configParser.getString("lunids", portChildNode) + ","
               + lunId;
      setNodeValue("lunids", portChildNode, lunIds);
      return true;
   }

   /**
    * @param processor
    * @return
    */
   public boolean addProcessor(Processor processor)
   {
      boolean success = false;
      Node result = null;
      try {
         Node processorsNode = configParser.getNode("processors", arrayNode);

         /*
          * check if the processor id already exists then generate a random id
          */
         String newProcessorId = null;
         if (isProcessorIdentifierAlreadyExists(processor.getSpidentifiers())) {
            newProcessorId = Util.getRandomAlphaNumericString(5);
         } else {
            newProcessorId = processor.getSpidentifiers();
         }
         org.w3c.dom.Document doc = processorsNode.getOwnerDocument();
         Element processorElement = doc.createElement("processor");
         Element spidElement = doc.createElement("spidentifiers");
         spidElement.appendChild(doc.createTextNode(newProcessorId));
         processorElement.appendChild(spidElement);
         processorsNode.appendChild(processorElement);

         Element ports = doc.createElement("ports");
         processorElement.appendChild(ports);

         if (null != processor.getPorts() && processor.getPorts().size() > 0) {

            for (Port port : processor.getPorts()) {
               addPort(port, newProcessorId);
            }

         }

      } catch (Exception e) {
         e.printStackTrace();
      }
      if (result != null)
         success = true;

      return success;
   }

   /**
    * If parent processor id is provided as null then it will insert the port in
    * the first available processor. If the processor id is not present then
    * also it inserts in the first available processor
    *
    * @param port port to insert
    * @param parentProcessorId
    * @return
    */
   public boolean addPort(Port port,
                          String parentProcessorId)
   {
      boolean success = false;
      Node result = null;
      try {
         Node processorChildNode = null;

         /*
          * if you find a processor with the provided one then use that
          */
         if (parentProcessorId != null) {
            NodeList list = configParser.getNodeList(
                     "processors/processor[spidentifiers='" + parentProcessorId
                              + "']", arrayNode);
            if (null != list && list.getLength() > 0) {
               processorChildNode = list.item(0);
            } else {
               /*
                * else use the first available processor node
                */
               processorChildNode = configParser.getNode(
                        "processors/processor", arrayNode);
            }
         } else {
            /*
             * if processor is not provided then pickup the first processor
             */
            processorChildNode = configParser.getNode("processors/processor",
                     arrayNode);
         }

         Node portsNode = configParser.getNode("ports", processorChildNode);
         Node portChildNode = configParser.getNode(
                  "processors/processor/ports/port", arrayNode);
         Node newPortChildNode = portChildNode.cloneNode(true);
         setNodeValue("porttype", newPortChildNode, port.getPortType());
         setNodeValue("portWwn", newPortChildNode, port.getPortWwn());
         setNodeValue("nodeWwn", newPortChildNode, port.getNodeWwn());
         setNodeValue("iscsiidentifier", newPortChildNode, port
                  .getIscsiidentifier());
         setNodeValue("alternateNames", newPortChildNode, port
                  .getAlternateNames());
         setNodeValue("lunids", newPortChildNode, port.getLunids());
         result = portsNode.appendChild(newPortChildNode);

      } catch (Exception e) {
         e.printStackTrace();
      }
      if (result != null)
         success = true;
      return success;
   }

   /**
    * @param iscsiHostInitiator
    * @return
    */
   public boolean addISCSIHostInitiator(ISCSIHostInitiator iscsiHostInitiator)
   {
      boolean success = false;
      Node result = null;
      try {
         Node hostInitiatorsNode = configParser.getNode("hostInitiators",
                  arrayNode);
         Node newIscsiHostInitiatorChildNode = configParser.getNode(
                  "iscsiHostInitiator", hostInitiatorsNode).cloneNode(true);

         org.w3c.dom.Document doc = hostInitiatorsNode.getOwnerDocument();

         setNodeValue("iScsiName", newIscsiHostInitiatorChildNode,
                  iscsiHostInitiator.getiScsiName());
         List<String> esxLunIdentifiers = iscsiHostInitiator
                  .getEsxLunIdentifiers();
         Element iscsiHostInitiatorIdElement = doc
                  .createElement("iscsiHostInitiatorId");
         iscsiHostInitiatorIdElement
                  .appendChild(doc.createTextNode(iscsiHostInitiator
                           .getIscsiHostInitiatorId()));
         newIscsiHostInitiatorChildNode
                  .appendChild(iscsiHostInitiatorIdElement);

         if (null != esxLunIdentifiers && esxLunIdentifiers.size() > 0) {

            for (int i = 0; i < esxLunIdentifiers.size(); i++) {
               if (i == 0) {
                  setNodeValue("esxLunIdentifier",
                           newIscsiHostInitiatorChildNode, esxLunIdentifiers
                                    .get(i));
               } else {

                  Element esxLunIdentifierElement = doc
                           .createElement("esxLunIdentifier");
                  esxLunIdentifierElement.appendChild(doc
                           .createTextNode(esxLunIdentifiers.get(i)));
                  esxLunIdentifierElement.appendChild(esxLunIdentifierElement);
               }
            }
         }
         result = hostInitiatorsNode
                  .appendChild(newIscsiHostInitiatorChildNode);

      } catch (Exception e) {
         e.printStackTrace();
      }
      if (result != null)
         success = true;
      return success;
   }

   /**
    * @param fcHostInitiator
    * @return
    */
   public boolean addFCHostInitiator(FCHostInitiator fcHostInitiator)
   {
      boolean success = false;
      Node result = null;
      try {
         Node hostInitiatorsNode = configParser.getNode("hostInitiators",
                  arrayNode);
         Node newFcHostInitiatorChildNode = configParser.getNode(
                  "fcHostInitiator", hostInitiatorsNode).cloneNode(true);

         org.w3c.dom.Document doc = hostInitiatorsNode.getOwnerDocument();

         Element fcHostInitiatorIdElement = doc
                  .createElement("fcHostInitiatorId");
         fcHostInitiatorIdElement.appendChild(doc
                  .createTextNode(fcHostInitiator.getFcHostInitiatorId()));
         newFcHostInitiatorChildNode.appendChild(fcHostInitiatorIdElement);

         setNodeValue("portWwn", newFcHostInitiatorChildNode, fcHostInitiator
                  .getPortWwn());
         setNodeValue("nodeWwn", newFcHostInitiatorChildNode, fcHostInitiator
                  .getNodeWwn());
         List<String> fcLunIdentifiers = fcHostInitiator.getEsxLunIdentifier();

         if (null != fcLunIdentifiers && fcLunIdentifiers.size() > 0) {

            for (int i = 0; i < fcLunIdentifiers.size(); i++) {
               if (i == 0) {
                  setNodeValue("esxLunIdentifier", newFcHostInitiatorChildNode,
                           fcLunIdentifiers.get(i));
               } else {
                  Element esxLunIdentifierElement = doc
                           .createElement("esxLunIdentifier");
                  esxLunIdentifierElement.appendChild(doc
                           .createTextNode(fcLunIdentifiers.get(i)));
                  newFcHostInitiatorChildNode
                           .appendChild(esxLunIdentifierElement);
               }
            }

         }

         result = hostInitiatorsNode.appendChild(newFcHostInitiatorChildNode);

      } catch (Exception e) {
         e.printStackTrace();
      }
      if (result != null)
         success = true;
      return success;
   }

   /**
    * writes the output xml file to output
    *
    * @throws Exception
    */
   public void writeXML(OutputStream stream)
      throws Exception
   {
      org.w3c.dom.Document doc = arrayNode.getOwnerDocument();
      Element arraysNode = doc.createElement("Arrays");
      arraysNode.appendChild(arrayNode);
      DOMSource source = new DOMSource(arraysNode);
      StreamResult result = new StreamResult(stream);
      TransformerFactory tFactory = TransformerFactory.newInstance();
      Transformer transformer = tFactory.newTransformer();
      transformer.transform(source, result);
   }

   /**
    * @param parentNodeName
    * @param context
    * @param value
    * @throws Exception
    */
   public void setNodeValue(String parentNodeName,
                            Object context,
                            String value)
      throws Exception
   {
      configParser.getNode(parentNodeName, context).getFirstChild()
               .setNodeValue(value);
   }

   /**
    * Checks if the provided processor identifier already exists
    *
    * @param processorIdentifier
    * @return
    * @throws Exception
    */
   public boolean isProcessorIdentifierAlreadyExists(String processorIdentifier)
      throws Exception
   {
      List<String> ids = new ArrayList<String>();
      NodeList processorsIdNodes = configParser.getNodeList(
               "processors/processor/spidentifiers", arrayNode);

      if (null != processorsIdNodes && processorsIdNodes.getLength() > 0) {

         for (int i = 0; i < processorsIdNodes.getLength(); i++) {
            ids.add(processorsIdNodes.item(i).getFirstChild().getNodeValue()
                     .replaceAll("[\r\n\t]", "").trim());
         }
      }
      return ids.contains(processorIdentifier);

   }

   /**
    * @param args
    * @throws Exception
    */
   public static void main(String args[])
      throws Exception
   {
      DiscoveryManager manager = new DiscoveryManager();
      manager.setArrayName("Hello World");

      // Lun l = new Lun();
      // manager.addLun(l.getDummyLun());
      //
      // FileSystem fs = new FileSystem();
      // manager.addFileSystem(fs.getDummyFileSystem());
      //
      // Processor processor = new Processor();
      // manager.addProcessor(processor.getDummyProcessor());

      // Port p = new Port();
      // manager.addPort(p.getDummyPort(), "11111");

      // ISCSIHostInitiator iscsi = new ISCSIHostInitiator();
      // manager.addISCSIHostInitiator(iscsi.getDummyISCSIHostInitiator());
      //
      FCHostInitiator fc = new FCHostInitiator();
      manager.addFCHostInitiator(fc.getDummyFCHostInitiator());

      manager.writeXML(System.out);
   }

   /**
    * Returns the config parser
    *
    * @return
    */
   private XmlParser getConfigParser()
   {

      XmlParser configParser = new XmlParser();
      boolean loadResourceError = false;
      try {
         configParser.loadResource(configFile);
      } catch (Exception e) {
         loadResourceError = true;
         System.out.println("Error loading the config.xml" + e);
      }
      return configParser;
   }

   /**
    * @return
    */
   public Document getTemplateXML()
   {
      return templateXML;
   }

   /**
    * @param templateXML
    */
   public void setTemplateXML(Document templateXML)
   {
      this.templateXML = templateXML;
   }

   public static String prettyFormat(String input,
                                     int indent)
   {
      try {
         Source xmlInput = new StreamSource(new StringReader(input));
         StringWriter stringWriter = new StringWriter();
         StreamResult xmlOutput = new StreamResult(stringWriter);
         TransformerFactory transformerFactory = TransformerFactory
                  .newInstance();
         transformerFactory.setAttribute("indent-number", indent);
         Transformer transformer = transformerFactory.newTransformer();
         transformer.setOutputProperty(OutputKeys.INDENT, "yes");
         transformer.transform(xmlInput, xmlOutput);
         return xmlOutput.getWriter().toString();
      } catch (Exception e) {
         throw new RuntimeException(e); // simple exception handling, please
         // review it
      }
   }

   public static String prettyFormat(String input)
   {
      return prettyFormat(input, 2);
   }
}
